#!/usr/bin/python
# -*- coding: utf-8 -*-

import random
import itertools


class Cryptogram(object):

  def __init__(self, name, msg=None, key=None, plain=None):
    super(Cryptogram, self).__init__()
    self.name = name
    self.msg = msg
    self.key = key
    self.plain = plain

  def hasMsg(self):
    return self.msg is not None

  def hasKey(self):
    return self.key is not None

  def hasPlain(self):
    return self.plain is not None

  def completeKeyMultiple(self, char=u' '):
    if not self.hasKey():
      return
    if self.hasPlain():
      self.plain += (len(self.plain) - (len(self.plain) % len(self.key))) \
          * char
    if self.hasMsg():
      self.msg += (len(self.msg) - (len(self.msg) % len(self.key))) * char

  def __str__(self):
    return """=== %s ===
  key: %s
  msg: %s
  plain: %s""" % (self.name and self.name.encode('utf-8'),
      self.key and self.key.encode('utf-8'),
      self.msg and self.msg.encode('utf-8'),
      self.plain and self.plain.encode('utf-8'))


class Bitsream(object):

  def __init__(self
      , baseText
      , byteSize
      , blockSize
      , alphabet
      , binarized=False):
    object.__init__(self)
    self.__bitStream = baseText
    self.__byteSize = byteSize
    self.__blockSize = blockSize
    self.__alphabet = alphabet
    self.__binarized = binarized

  @staticmethod
  def __dec2bin(n, l=8):
    s = ''
    while n:
      s = ((n % 2) and '1' or '0') + s
      n = n >> 1
    while len(s) < l:
      s = '0' + s
    return s

  def binarize(self):
    if not self.__binarized:
      self.__bitStream = ''.join([Bitsream.__dec2bin(
        self.__alphabet.encode(c)
        , self.__byteSize)
        for c in self.__bitStream])
      self.__binarized = True
    return self.__bitStream

  def getBlocks(self):
    if self.__binarized:
      blockSize = self.__blockSize * self.__byteSize
    else:
      blockSize = self.__blockSize
    blocks = []
    for i in xrange(0
        , len(self.__bitStream)
        , blockSize):
      blocks.append(self.__bitStream[i:i + blockSize])
    return blocks

  def rotateRight(self
      , size):
    if not self.__binarized:
      self.binarize()
    self.__bitStream = ''.join([block[-size:] + block[:-size]
      for block in self.getBlocks()])

  def rotateLeft(self
      , size):
    if not self.__binarized:
      self.binarize()
    self.__bitStream = ''.join([block[size:] + block[:size]
      for block in self.getBlocks()])

  def alphabetize(self):
    if self.__binarized:
      finalBitStream = ''
      for i in xrange(0
          , len(self.__bitStream)
          , self.__byteSize):
        finalBitStream += self.__alphabet.decode(int(
          self.__bitStream[i:i + self.__byteSize]
          , 2))
      self.__bitStream = finalBitStream
      self.__binarized = False
    return self.__bitStream

  def XOR(self, bitstream):
    finalBitStream = ''
    for i, j in itertools.izip(self.binarize()
        , itertools.cycle(bitstream.binarize())):
      finalBitStream += str(int(i, 2) ^ int(j, 2))
    return Bitsream(finalBitStream
        , self.__byteSize
        , self.__blockSize
        , self.__alphabet
        , binarized=True)

  def get(self):
    return self.__bitStream

class Alphabet(object):

  def __init__(self, baseText):
    super(Alphabet, self).__init__()
    self.__alphabet = baseText

  def compress(self, lastAlphabet, preExist=True, existOrder=1, lastOrder=1):
    existProcessed = u''
    for c in self.__alphabet[::existOrder]:
      if c not in existProcessed:
        existProcessed += c

    lastProcessed = u''
    for c in str(lastAlphabet).decode('utf-8')[::lastOrder]:
      if c not in existProcessed:
        lastProcessed += c

    if preExist:
      self.__alphabet = existProcessed + lastProcessed
    else:
      self.__alphabet = lastProcessed + existProcessed

  def compressPrePost(self, lastAlphabet):
    existProcessed = u''
    for i, c in enumerate(self.__alphabet):
      if c not in existProcessed:
        if i % 2:
          existProcessed += c
        else:
          existProcessed = c + existProcessed

    for i, c in enumerate(str(lastAlphabet).decode('utf-8')):
      if c not in existProcessed:
        if i % 2:
          existProcessed = c + existProcessed
        else:
          existProcessed += c
    self.__alphabet = existProcessed

  def shuffle(self, lastAlphabet):
    # Supposed lastAlphabet bigger than this, if not => IndexError
    self.compress(lastAlphabet)
    lastAlphabet = str(lastAlphabet).decode('utf-8')
    selfLength = len(self.__alphabet)
    self.__alphabet, baseAlphabet = '', self.__alphabet
    for i in xrange(selfLength):
      if lastAlphabet[i] not in self.__alphabet:
        self.__alphabet += lastAlphabet[i]
      if baseAlphabet[i] not in self.__alphabet:
        self.__alphabet += baseAlphabet[i]
    for c in lastAlphabet[i + 1:]:
      if c not in self.__alphabet:
        self.__alphabet += c

  def invert(self):
    self.__alphabet = self.__alphabet[::-1]

  def encode(self, c, reverse=False):
    return self.__alphabet[::reverse and -1 or 1].find(c)

  def decode(self, i, reverse=False):
    return self.__alphabet[::reverse and -1 or 1][i % len(self.__alphabet)]

  def randChoice(self):
    return random.choice(self.__alphabet)

  def __str__(self):
    return unicode(self.__alphabet).encode('utf-8')

  def __len__(self):
    return len(self.__alphabet)

  def diff(self, msg1, msg2):
    return Cryptogram(msg1.name + '-' + msg2.name,
        u''.join([self.decode(self.encode(c) - self.encode(d)) for c, d in
          zip(msg1.msg, msg2.msg)]))


class SpanishAlphabet(Alphabet):

  def __init__(self, space=u' '):
    super(SpanishAlphabet, self).__init__(
        u'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ%s' % (space or '')
      )


class SortedSpanishAlphabet(Alphabet):

  def __init__(self, space=u' '):
    super(SortedSpanishAlphabet, self).__init__(
        u'%sEAOSNRILDUTCMPQYBHVGJFZÑXWK' % (space or '')
      )


class EnglishAlphabet(Alphabet):

  def __init__(self, space=u' '):
    super(EnglishAlphabet, self).__init__(
        u'ABCDEFGHIJKLMNOPQRSTUVWXYZ%s' % (space or '')
      )


class Llamame36Alphabet(Alphabet):

  def __init__(self):
    super(Llamame36Alphabet, self).__init__(
        u'ABCDEFGHIJKLMNOPQRSTUVWXYZ_123456789'
      )


class DASKAlphabet(Alphabet):

  def __init__(self):
    super(DASKAlphabet, self).__init__(
        u'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ_,.;:'
      )


class SortedDASKAlphabet(Alphabet):

  def __init__(self):
    super(SortedDASKAlphabet, self).__init__(
        u'_EAOSNRILDUTCMPQYBHVGJFZÑXWK,.;:'
      )

